package com.zwk.gulimall.product.advice;

import com.zwk.gulimall.product.annotation.Overall;
import com.zwk.gulimall.product.annotation.Relation;
import com.zwk.gulimall.product.annotation.RelationMethod;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

@Aspect
//@Component
public class RelationAdvice implements ApplicationContextAware {

    private ApplicationContext context;
    /**
     * 用来存储实体类对应的methods
     */
    private Map<Class,List<BeanWithMethod>> relationMap=new HashMap<>();
    /**
     * 用来存储service类符合条件的method
     */
    private Map<Object,List<Method>> serviceMap=new HashMap<>();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context=applicationContext;
    }

    @Pointcut("@annotation(com.zwk.gulimall.product.annotation.Overall)")
    private void overallOnMethod(){}
    @Pointcut("@args(com.zwk.gulimall.product.annotation.Relation)")
    private void overallOnClass(){}

    @Around("overallOnMethod()")
    public Object updateRelation(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("aop");
        Object[] args = joinPoint.getArgs();
        List<ArgWithClass> objWithClassList = this.getAnnotationClass(Relation.class, args);
        for (ArgWithClass argWithClass : objWithClassList) {
            this.getRelationMethod(argWithClass.clazz);
            this.invokeRelationMethod(argWithClass.clazz,argWithClass.argument);
        }

//
//        Class<?> relationClass = relation.getClass();
//        List<Relation> annotationList=(List)this.getAnnotationList(relationClass, Relation.class);
//        Set<Class> serviceSet = new HashSet<>();
//        //拿到关联service的Class
//        annotationList.forEach((item)->{
//            serviceSet.addAll(Arrays.asList(item.value()));
//        });
//        Iterator<Class> iterator = serviceSet.iterator();
//        while (iterator.hasNext()) {
//            Class service = iterator.next();
//
//            Method[] methods = service.getMethods();
//            Method updateDetail = methods[0];
//            updateDetail.isAnnotationPresent(RelationMethod.class);
//            updateDetail.invoke(context.getBean(service),relation);
//        }
        Object proceed = joinPoint.proceed(args);
        return proceed;
    }


    /**
     * 返回指定注解的参数的Class
     * @param annotationClass:指定注解
     * @param objects:参数
     * @return
     */
    public List<ArgWithClass> getAnnotationClass(Class annotationClass,Object[] objects){
        List<ArgWithClass> classList=new ArrayList<>();
        for (Object object : objects) {
            Class<?> clazz = object.getClass();
            if (clazz.isAnnotationPresent(annotationClass)) {
                classList.add(new ArgWithClass(object,clazz));
            }
        }
        return classList;
    }

    /**
     * 返回实体类关联的service实例和方法
     * @param clazz
     * @return
     */
    private List<BeanWithMethod> getRelationMethod(Class clazz){
        if (this.relationMap.containsKey(clazz)){
            return this.relationMap.get(clazz);
        }
        List<BeanWithMethod> relationList=new ArrayList<>();
        Relation annotation = (Relation) clazz.getAnnotation(Relation.class);
        //实体类对应的service
        Class[] services = annotation.value();
        Class<RelationMethod> annotationClass = RelationMethod.class;
        //获取注解对应所有Class中带有RelationMethod注解的Method，并将其存入到上面的methodList中
        for (Class service : services) {
            //获取service中带有注解的方法
            List<Method> annotationMethods = this.getAnnotationMethods(service, annotationClass);
            Object bean=context.getBean(service);
            //获取service中参数是clazz的方法
            Method relationMethod = annotationMethods.stream().filter(method -> {
                for (Class<?> parameterType : method.getParameterTypes()) {
                    if (clazz.equals(parameterType)) {
                        return true;
                    }
                }
                return false;
            }).findFirst().get();
            relationList.add(new BeanWithMethod(bean,relationMethod));
//            //获取service对应的所有方法
//            List<Method> serviceList=new ArrayList<>();
//            //是否找到实体类关联的methods
//            boolean find=false;
//            Method[] methods = service.getDeclaredMethods();
//            for (Method method : methods) {
//                //查询service中带有relation注解的方法，并将其放入serviceList
//                if (method.isAnnotationPresent(annotationClass)) {
//                    serviceList.add(method);
//                    //查找实体类关联的method，并将其放入relationList中
//                    if (!find){
//                        Class<?>[] parameterTypes = method.getParameterTypes();
//                        for (Class<?> parameterType : parameterTypes) {
//                            if (parameterType.equals(clazz)) {
//                                find=true;
//                                relationList.add(method);
//                                break;
//                            }
//                        }
//                    }
//                }
//            }
//            this.serviceMap.put(service,serviceList);
        }
        this.relationMap.put(clazz,relationList);
        return relationList;
    }

    /**
     * 返回service中带有指定注解的方法
     * @param service
     * @param annotation
     * @return
     */
    private List<Method> getAnnotationMethods(Class service,Class annotation){
        if (this.serviceMap.containsKey(service)){
            return this.serviceMap.get(service);
        }
        List<Method> serviceList=new ArrayList<>();
        Method[] serviceMethods = service.getDeclaredMethods();
        for (Method method : serviceMethods) {
            if (method.isAnnotationPresent(annotation)) {
                serviceList.add(method);
            }
        }
        this.serviceMap.put(service,serviceList);
        return serviceList;
    }

    /**
     * 执行argWithClass关联方法
     * @param clazz
     * @param argument
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public void invokeRelationMethod(Class clazz,Object argument) throws InvocationTargetException, IllegalAccessException {
        List<BeanWithMethod> beanWithMethods = this.getRelationMethod(clazz);
        for (BeanWithMethod beanWithMethod : beanWithMethods) {
            beanWithMethod.invoke(argument);
        }
//
//
//        Relation annotation = (Relation) clazz.getAnnotation(Relation.class);
//        Class[] services = annotation.value();
//        for (Class service : services) {
//            List<Method> methods = this.serviceMap.get(service);
//            Object bean = context.getBean(service);
//            for (Method method : methods) {
//                method.invoke(bean,argument);
//            }
//        }
    }

    /**
     * 获取指定类中属性上带有某个注解的注解值
     * @param clazz  ：我们的类
     * @param annotationClass：指定的注解的Class
     * @return
     */
    public List<Annotation> getAnnotationList(Class clazz,Class annotationClass){
        Field[] fields = clazz.getDeclaredFields();
        List<Annotation> annotationList=new ArrayList<>();
        for (Field field : fields) {
            if (field.isAnnotationPresent(annotationClass)) {
                annotationList.add(field.getAnnotation(annotationClass));
            }
        }
        return annotationList;
    }


    /**
     * 保存实参和实参对应的class结构
     */
    private static class ArgWithClass{
        public Object argument;
        public Class clazz;


        public ArgWithClass(Object argument, Class clazz) {
            this.argument = argument;
            this.clazz = clazz;
        }
    }

    @AllArgsConstructor
    private static class BeanWithMethod{
        public Object bean;
        public Method method;
        public void invoke(Object... args) throws InvocationTargetException, IllegalAccessException {
            this.method.invoke(bean,args);
        }
    }
}
